# Copyright Mads D. Kristensen, 2009
# This file is provided under the GPL General Public License v. 3

# This implements a simple facade on top of the AugIM image operations. 
# The protocol used here is quite simple:
# 1) The client application connects to this facade on localhost:4753
# 2) After connecting the client can issue commands using the following protocol:
#    a) The client sends a length-prefixed string containing a four-byte unsigned
#       integer (the length of the command) and the name of the command.
#    b) The facade answers this with either 'OK', which means "I know that command
#       please send me the input", or 'ERROR', which means "unknown command".
#    c) Having received an 'OK' from the facade the client may now proceed to send
#       the input of the command. The format of this transfer varies from command 
#       to command.

from __future__ import with_statement
from timedsocket import *
import scavenger
import imageops
import struct

class AugIMFacade(object):
    COMMANDS = ['shutdown', 'sharpen', 'blur', 'brightness', 'contrast', 'color', 'rotate', 
                'flip_horizontal', 'flip_vertical', 'invert', 'mail', 'picasa', 'scale',
                'picasa_upload', 'send_mail']

    def __init__(self, port = 4753):
        super(AugIMFacade, self).__init__()
        self._port = port

        # Set up the socket listening for incoming connections.
        self._sock = TimedSocket()
        self._sock.bind(('localhost', self._port))
        self._sock.listen(2)

    def _wait_for_connection(self):
        """
        This method waits for an incomming connection attempt.
        If the connection is succesful it returns the new socket.
        """
        socket = None
        while True:
            try:
                socket = self._sock.accept()
                break
            except Timeout:
                continue
        return socket
            
    def _receive_image(self, socket):
        # Receive the image.
        try:
            image = socket.recv_lp();
            socket.send_lp('OK')
        except:
            socket.send_lp('ERROR')
            raise Exception('Error receiving image.')
        return image

    def _receive_string(self, socket):
        return self._receive_image(socket)

    def _receive_float(self, socket):
        # Receive the float.
        try:
            value_str = socket.recv_lp()
            value = struct.unpack("!f", value_str)[0]
            socket.send_lp('OK')
        except:
            socket.send_lp('ERROR')
            raise Exception('Error receiving float argument.')
        return value

    def _receive_int(self, socket):
        # Receive the int.
        try:
            value_str = socket.recv_lp()
            value = struct.unpack("!I", value_str)[0]
            socket.send_lp('OK')
        except:
            socket.send_lp('ERROR')
            raise Exception('Error receiving int argument.')
        return value

    def _receive_image_and_float(self, socket):
        image = self._receive_image(socket)
        value = self._receive_float(socket)
        return (image, value)

    def _sharpen(self, socket):
        image, factor = self._receive_image_and_float(socket)
        image = imageops.sharpen(image, factor)
        socket.send_lp(image)

    def _blur(self, socket):
        image, factor = self._receive_image_and_float(socket)
        image = imageops.blur(image, factor)
        socket.send_lp(image)

    def _brightness(self, socket):
        image, factor = self._receive_image_and_float(socket)
        image = imageops.brightness(image, factor)
        socket.send_lp(image)

    def _contrast(self, socket):
        image, factor = self._receive_image_and_float(socket)
        image = imageops.contrast(image, factor)
        socket.send_lp(image)

    def _color(self, socket):
        image, factor = self._receive_image_and_float(socket)
        image = imageops.color(image, factor)
        socket.send_lp(image)

    def _rotate(self, socket):
        image, factor = self._receive_image_and_float(socket)
        image = imageops.rotate(image, factor)
        socket.send_lp(image)

    def _invert(self, socket):
        image = self._receive_image(socket)
        image = imageops.invert(image)
        socket.send_lp(image)

    def _flip_horizontal(self, socket):
        image = self._receive_image(socket)
        image = imageops.flip_horizontal(image)
        socket.send_lp(image)

    def _flip_vertical(self, socket):
        image = self._receive_image(socket)
        image = imageops.flip_vertical(image)
        socket.send_lp(image)

    def _scale(self, socket):
        image = self._receive_image(socket)
        width = self._receive_int(socket)
        height = self._receive_int(socket)
        image = imageops.scale(image, (width, height))
        socket.send_lp(image)

    def _picasa_upload(self, socket):
        image = self._receive_image(socket)
        username = self._receive_string(socket)
        password = self._receive_string(socket)
        album = self._receive_string(socket)
        title = self._receive_string(socket)
        description = self._receive_string(socket)
        imageops.picasa_upload(image, username, password, album, title, description)
        socket.send_lp('OK')

    def _send_mail(self, socket):
        image = self._receive_image(socket)
        server = self._receive_string(socket)
        port = self._receive_int(socket)
        sender = self._receive_string(socket)
        recipient = self._receive_string(socket)
        subject = self._receive_string(socket)
        message = self._receive_string(socket)
        username = self._receive_string(socket)
        password = self._receive_string(socket)
        imageops.send_mail(server, port, sender, recipient, subject, message, username, password, image, 'image/jpeg')
        socket.send_lp('OK')        

    def _wait_for_command(self, socket):
        # Wait for a command to arrive.
        while True:
            try:
                command = socket.recv_lp()
            except Timeout:
                continue

            # Handle the command request.
            command = command.lower()
            if not command in AugIMFacade.COMMANDS:
                socket.send_lp('ERROR')
                continue
            else:
                socket.send_lp('OK')

            # Switch out between the different commands.
            if command == 'shutdown':
                self._shutdown = True
                break
            elif command == 'sharpen':
                self._sharpen(socket)
            elif command == 'blur':
                self._blur(socket)
            elif command == 'brightness':
                self._brightness(socket)
            elif command == 'contrast':
                self._contrast(socket)
            elif command == 'color':
                self._color(socket)
            elif command == 'rotate':
                self._rotate(socket)
            elif command == 'invert':
                self._invert(socket)
            elif command == 'flip_horizontal':
                self._flip_horizontal(socket)
            elif command == 'flip_vertical':
                self._flip_vertical(socket)
            elif command == 'scale':
                self._scale(socket)
            elif command == 'picasa_upload':
                self._picasa_upload(socket)
            elif command == 'send_mail':
                self._send_mail(socket)
            else:
                print command #DEBUG
        
        # Done serving that client.
        print 'Client leaving.'
            

    def serve(self):
        self._shutdown = False
        while not self._shutdown:
            # Start listening to incoming connection requests. This facade accepts
            # only one connection at the time.
            socket = self._wait_for_connection()
            print 'Got connection' #DEBUG
     
            # We have a connection. Start waiting for requests from the client.
            try:
                self._wait_for_command(socket)
            except Exception, e:
                print 'Connection broken:', e.args[0]

    def close(self):
        self._sock.close()

facade = AugIMFacade()
facade.serve()
facade.close()
